package club.ruanx.spider;

import club.ruanx.spider.common.GlobalThreadPool;
import club.ruanx.spider.common.enumeration.SpiderLogTypeEnum;
import club.ruanx.spider.constraint.DoubanConstraint;
import club.ruanx.spider.entity.DoubanMovie;
import club.ruanx.spider.entity.SpiderRecord;
import club.ruanx.spider.filter.SpiderUrlFilter;
import club.ruanx.spider.service.DoubanMovieService;
import club.ruanx.spider.service.SpiderRecordService;
import club.ruanx.spider.util.JsoupUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 阮胜
 * @date 2018/11/12 21:50
 */
@Component
@Slf4j
public class DouBanSpider {


    private final SpiderUrlFilter spiderUrlFilter;
    private final GlobalThreadPool globalThreadPool;
    private final DoubanMovieService doubanMovieService;
    private final SpiderRecordService spiderRecordService;

    public DouBanSpider(SpiderUrlFilter spiderUrlFilter, GlobalThreadPool globalThreadPool, DoubanMovieService doubanMovieService, SpiderRecordService spiderRecordService) {
        this.spiderUrlFilter = spiderUrlFilter;
        this.globalThreadPool = globalThreadPool;
        this.doubanMovieService = doubanMovieService;
        this.spiderRecordService = spiderRecordService;
    }


    public Set<String> fetchSubjectsWithTypeAndLimit(String type, int limit, int pageStart) throws IOException {
        String startUrl = String.format(DoubanConstraint.START_URL, type, limit, pageStart);
        return fetchSubjectIds(startUrl);
    }

    private Set<String> fetchSubjectIds(String url) throws IOException {
        Document document = JsoupUtils.connect(url).ignoreContentType(true).timeout(5000).get();
        String json = document.text();
        JSONObject jsonObject = JSON.parseObject(json);
        JSONArray subjects = jsonObject.getJSONArray("subjects");
        HashSet<String> idSet = new HashSet<>();
        for (int i = 0; i < subjects.size(); i++) {
            String movieId = subjects.getJSONObject(i).getString("id");
            idSet.add(movieId);
        }
        return idSet;
    }

    /**
     * 开始爬虫
     */
    public void process(String type, int limit, int pageStart) throws IOException {
        log.info("开启豆瓣爬虫");
        Set<String> idSet = fetchSubjectsWithTypeAndLimit(type, limit, pageStart);
        fetchByIds(idSet);
    }

    public void fetchByIds(Set<String> idSet) {
        if (CollectionUtils.isEmpty(idSet)) {
            return;
        }
        List<String> urlList = idSet.stream()
                .map(DoubanConstraint.MOVIE_DETAILS_API::concat)
                .filter(url -> !spiderUrlFilter.contains(url))
                .collect(Collectors.toList());
        CountDownLatch latch = new CountDownLatch(urlList.size());
        urlList.forEach(url -> globalThreadPool.execute(() -> {
            String logStr = "";
            try {
                DoubanMovie doubanMovie = fetchDetailsByApiWithMovieUrl(url);
                doubanMovie.setSynchronization(false);
                doubanMovieService.save(doubanMovie);
                spiderUrlFilter.add(url);
                logStr = String.format("新增电影 : %s", doubanMovie.getTitle());
                log.info(logStr);
            } catch (IOException e) {
                logStr = String.format("获取电影详情失败 : %s", e.getMessage());
                log.error(logStr);
            } finally {
                latch.countDown();
                SpiderRecord spiderRecord = SpiderRecord.builder()
                        .type(SpiderLogTypeEnum.DOU_BAN.name())
                        .url(url)
                        .remark(logStr)
                        .createTime(new Date())
                        .build();
                spiderRecordService.save(spiderRecord);
            }
        }));
        try {
            latch.await(10, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        }
        log.info("爬虫执行完毕");
    }


    /**
     * 使用豆瓣官方API获取电影详情
     *
     * @param url 电影的URL
     * @return
     * @throws IOException
     */
    private DoubanMovie fetchDetailsByApiWithMovieUrl(String url) throws IOException {
        String json = JsoupUtils.connect(url).ignoreContentType(true).get().text();
        return JSON.parseObject(json, DoubanMovie.class);
    }

    /**
     * 使用豆瓣官方API获取电影详情
     *
     * @param movieId 电影ID
     * @return
     * @throws IOException
     */
    public DoubanMovie fetchDetailsByApiWithMovieId(String movieId) throws IOException {
        return fetchDetailsByApiWithMovieUrl(DoubanConstraint.MOVIE_DETAILS_API.concat(movieId));
    }

    /**
     * 根据电影ID获取关联的电影
     *
     * @param movieId
     * @return
     * @throws IOException
     */
    public String[] fetchRecommendMovieByMovieId(String movieId) throws IOException {
        Document doc = Jsoup.connect(DoubanConstraint.MOVIE_DETAILS_URL.concat(movieId)).get();
        return doc.select(".recommendations-bd").text().split(" ");
    }

}
